#include "Server.h"

Server::Server(unsigned int start, unsigned int argc, char * const argv[], bool d) : daemon(d)
{
	if (argc-start == 2)
		init(atoi(argv[start+0]), atoi(argv[start+1]), atoi(argv[start+2]));
	else if (argc-start == 2)
		init(atoi(argv[start+0]), atoi(argv[start+1]));
	else if (argc-start == 1)
		init(atoi(argv[start+0]));
	else
		init();
}

Server::~Server( void )
{
	// TODO: Disconect from client
	// TODO: Notify client that you have disconnected.
}

void Server::init(void)
{
	init(MAX_CONNECTIONS, TEXT_PORT, FILE_PORT);
}

void Server::init(unsigned int max)
{
	init(max, TEXT_PORT, FILE_PORT);
}

void Server::init(unsigned int max, unsigned int port1)
{
	init(max, port1, FILE_PORT);
}

void Server::init(unsigned int max, unsigned int port1, unsigned int port2)
{
	max_fd = -1;
	// TODO: Set the max number of Clients allowed
	// TODO: Set the port you will recieve messages on
	// TODO: Set the port number for transfers

	/* create a socket
	 IP protocol family (PF_INET)
	 TCP protocol (SOCK_STREAM)
	 */
     // TODO: Open a Socket

    //TODO: Set Socket Options

	my_addr.sin_family = AF_INET;			// host byte order
	my_addr.sin_port = htons(messagesPort);	// short, network byte order
	my_addr.sin_addr.s_addr = INADDR_ANY;	// automatically fill with my IP
	memset(&(my_addr.sin_zero), '\0', 8);	// zero the rest of the struct

	//TODO: Bind the port to the ip address
	//TODO: Check to make sure the Bind worked
	//TODO: Check to make sure you are listning for more Clients


	// Clear out all socket sets
	FD_ZERO(&clients_set);

	// Add servers connection socket to set
	FD_SET(connectfd, &clients_set);
}

void Server::acceptConnection( int socket )
{
	//TODO: Set the clint connection pointer
	//TODO: Create a new dispatch
	//TODO: Check to see if using a valid Socket
    //TODO: Create a new dispatch
    //TODO: Allow the Dispatch to hold Socket information? (Dale)
	//TODO: Take he information fromt he socket

	//TODO: CHeck to see if they gave a username

		//Add client to master client socket set. (file descriptors)
		FD_SET(socket, &clients_set);
		if((int)socket > max_fd) max_fd = socket;

		//TODO: Notify clients of connection

		//TODO: IF there is more than one user in the chat room, tell the user what other users are there
		//HINT: use listUsers()

}

void Server::notifyClients( Dispatch *dispatch, ClientConnection* host )
{
	ClientConnection *client;
	// ClientConnection list iterator
	std::list<ClientConnection*>::iterator c_it;

	ifprintf("Dispatch - %u : %s\n", (unsigned int)dispatch->getFlag(), dispatch->getData().c_str());

	// Forward all messags (for now) to other clients
	for ( c_it = clients.begin(); c_it != clients.end(); c_it++ )
	{
		client = (*c_it);

		// Do not send ones own message back to self
		if( client == host ) continue;

		// Try to send data to clients socket
		if( !dispatch->release( client->socket ) )
			ifprintf("not sent to %s\n", client->username.c_str());
		else
			ifprintf("sent to %s\n", client->username.c_str());
	}
}

void Server::disconnectClient( ClientConnection* client )
{
	Dispatch *notification = new Dispatch(CLIENT_DISCONNECT, client->username);

	// Remove socket from master socket set (Stop listening to it)
	FD_SET(client->socket, &clients_set);

#ifdef __WIN32__
	closesocket(client->socket);
#else
	close(client->socket);
#endif

	ifprintf("Disconnecting client %d (%s).\n", client->socket, client->username.c_str());

	// Pass along that same message to other clients
	notifyClients(notification, client);
	delete notification;
}

void Server::setMaxFD(void)
{
	// ClientConnection list iterator
	std::list<ClientConnection*>::iterator c_it;
	max_fd = -1;
	for ( c_it = clients.begin(); c_it != clients.end(); c_it++ )
		max_fd = (*c_it)->socket > max_fd ? (*c_it)->socket : max_fd;
}

std::string Server::listUsers( ClientConnection *ignore )
{
	std::string list;
	std::list<ClientConnection*>::iterator c_it;
	for ( c_it = clients.begin(); c_it != clients.end(); c_it++ )
	{
		if ((*c_it) == ignore) continue;
		if (c_it != clients.begin())
			list += ", ";
		list += (*c_it)->username;
	}
	return list;
}

void Server::run(void)
{
	ClientConnection *client;

	// Dispatches for incoming and outgoing messages
	Dispatch *incomming, *outgoing;

	// File Descriptor set for clients. Gets reset each loop.
	fd_set readfrom;
	FD_ZERO(&readfrom);

	// Socket Address structure for new connections
	struct sockaddr_in new_addr;
	socklen_t sin_size = sizeof(struct sockaddr_in);

	// ClientConnection list iterator
	std::list<ClientConnection*>::iterator c_it;

	while(1)
	{
		// This acts as the clear and reset for the socket set we're working with
		// since the set gets modified on 'select(...)'
		readfrom = clients_set;

		// select() blocks until there is something to read on one of the file descriptors
		if(max_fd != -1 && select(max_fd+1, &readfrom, NULL, NULL, NULL) < 0)
			return perror("select");

		// New connection?
		if(FD_ISSET(connectfd, &readfrom))
			acceptConnection(accept(connectfd, (struct sockaddr *)&new_addr, &sin_size));


		// For every connected client, process packets
		for ( c_it = clients.begin(); c_it != clients.end(); c_it++ )
		{
			// De-reference the iterator to get the client at position.
			client = *c_it;

			// Is there something to read from the client?
			if( FD_ISSET(client->socket, &readfrom) )
			{
				incomming = new Dispatch();
				incomming->receive(client->socket);

				switch (incomming->getFlag())
				{
					case CLIENT_DISCONNECT:
						disconnectClient(client);

						outgoing = new Dispatch(SERVER_DISCONNECT, "Graceful");
						outgoing->release(client->socket);
						delete outgoing;
						clients.erase(c_it++);

						// Reset and find the highest file descriptor
						setMaxFD();

						break;
					case CLIENT_MESSAGE:
						outgoing = new Dispatch(CLIENT_MESSAGE, std::string() + client->username + " : " + incomming->getData());
						// Notify all clients of outgoing dispatch. (Optional, do not notify 'client')
						notifyClients(outgoing, client);
						delete outgoing;

						break;

					case LIST_USERS:
						outgoing = new Dispatch(LIST_USERS, listUsers());
						outgoing->release(client->socket);
						delete outgoing;
						break;

					case CLIENT_REQUEST_NAME:

						// We need the server to be able to change the clients username.
						// For instance, when we start it up in silent server mode, we're going
						// to supply a default username... but what if someone connects with the same
						// default username? we need to say "no sorry, you have to use default_name_
						// or default_name__ and so on. Like IRC does :D.

						outgoing = new Dispatch(SERVER_CLIENT_RENAME, client->username + " is now known as " + incomming->getData());
						client->username = incomming->getData();
						notifyClients(outgoing, client);
						delete outgoing;

						break;

					case SEND_FILE:
						break;

					case CORRUPT:
					default:
						if (++client->corrupt >= CORRUPT_CLIENT_THRESHOLD)
						{
							ifprintf("(%u) Got too many corrupt packets from %s...\n", client->corrupt, client->username.c_str());
							disconnectClient(client);
							clients.erase(c_it++);

							// Reset and find the highest file descriptor
							setMaxFD();

						} else {
							ifprintf("(%u) Got a packet we didn't understand from %s...\n", client->corrupt, client->username.c_str());
						}
						break;
				}
				delete incomming;
			}
		}
	}
}
